在 Kubernetes 中,StatefulSet 是一種專門用來管理有狀態應用的工作負載控制器。與無狀態應用不同,有狀態應用通常需要每個 Pod 有穩定的網絡標識符(如 DNS 名稱)和持久性存儲。StatefulSet 保證了 Pod 的穩定標識、穩定存儲和有序部署、擴展以及刪除。
StatefulSet 的設計目的是解決有狀態應用中的一些常見需求,如:
podname-0, podname-1),而 Deployment 中的 Pod 是無狀態的,每個 Pod 的名稱和 IP 可能會變化。以下是一個簡單的 StatefulSet 組態檔範例:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
serviceName: "nginx":指定 StatefulSet 所屬的 Headless Service,用於管理 Pod 的 DNS 名稱和連接。replicas: 3:定義 3 個 Pod 副本,這些 Pod 是有狀態的,且每個 Pod 都會獲得一個唯一的名稱和持久存儲。volumeClaimTemplates:每個 Pod 都會創建一個名為 www 的 PersistentVolumeClaim,並請求 1Gi 的存儲空間。/usr/share/nginx/html,確保每個副本的數據是隔離的。接下來,我們將使用以下組態檔案來建立一個 StatefulSet 及其所依賴的 Service。該配置將建立一個無頭服務(Headless Service)nginx,以便發布 StatefulSet web 中各個 Pod 的 IP 地址。
組態檔案: sts.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
這個組態檔案包含:
nginx 的 headless serviceweb 的 statefulsett1 ,使用 --watch flag 監控 Pod 的變化kubectl get pods --watch -l app=nginx
kubectl apply -f sts.yaml
t1  終端,觀察 Pod 的變化NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          4s
web-0   0/1     ContainerCreating   0          4s
web-0   1/1     Running             0          14s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          5s
web-1   0/1     ContainerCreating   0          5s
web-1   1/1     Running             0          14s
web-2   0/1     Pending             0          0s
web-2   0/1     Pending             0          4s
web-2   0/1     ContainerCreating   0          4s
web-2   1/1     Running             0          5s
可以看到,3 個 Pod 是依序建立的,等到前一個 Pod 狀態變成 Running 後,才會開始建立下一個 Pod,直到全部 Pod 完成。
nginx StatefulSet 的 Podkubectl get pods -l app=nginx
---
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          5m17s
web-1   1/1     Running   0          5m3s
web-2   1/1     Running   0          4m49s
for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'hostname'; done
---
web-0
web-1
web-2
可以看到,StatefulSet 建立的 Pod 使用的是累進的數字當作 hostname,這也讓 Pod 變得更好預測。
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
之前的章節我們有提到,Headless Service 綁定 StatefulSet 的 Pod label,每個符合 selector 的 Pod 都會獲得一個 DNS 名稱。格式為 <pod-name>.<service-name>.<namespace>.svc.cluster.local 。
nslookup web-0.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-0.nginx
Address 1: 10.244.1.4 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-1.nginx
Address 1: 10.244.2.4 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-2.nginx
Address 1: 10.244.2.6 web-2.nginx.default.svc.cluster.local
可以看到,我們的確可以透過 DNS 名稱分別訪問每個 Pod。
接下來,我們來驗證 IP 變動的影響。
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
這樣會讓 Pod 重新建立,它們會被叢集分配新的 IP。
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
nslookup web-0.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-0.nginx
Address 1: 10.244.1.5 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-1.nginx
Address 1: 10.244.2.8 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-2.nginx
Address 1: 10.244.2.9 web-2.nginx.default.svc.cluster.local
可以看到 pod 對應的 IP 已經改變了,但我們依然可以使用相同的 DNS 名稱訪問對應的 Pod。
這就是為什麼不要在其他應用中使用 StatefulSet 中特定 Pod 的 IP 地址進行連接,因為 Pod 一旦重啟 IP 就會改變。
StatefulSet 控製器建立了三個 PersistentVolumeClaims, 繫結到三個 PersistentVolumes。我們來驗證一下。
kubectl get pvc -l app=nginx
---
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
www-web-0   Bound    pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c   1Gi        RWO            standard       <unset>                 67m
www-web-1   Bound    pvc-ee106200-53cd-40cc-8cfb-b489eff8a259   1Gi        RWO            standard       <unset>                 67m
www-web-2   Bound    pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1   1Gi        RWO            standard       <unset>                 66m
NginX Web 伺服器默認會載入位於 /usr/share/nginx/html/index.html 的 index 檔案。 StatefulSet spec 中的 volumeMounts 欄位保證了 /usr/share/nginx/html 資料夾由一個 PersistentVolume 卷支援。
index.html 檔案並for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2
可以看到,即使 Pod 被重新建立,StatefulSet 依然會自動為新的 Pod 繫結對應的 PV。
我們可以將 StatefulSet 視為 ReplicaSet 的一個高級變體,因此他也有 ReplicaSet 的擴容/縮容功能。
t1 終端,重新使用 --watch flag 監控 Pod 的變化kubectl get pods --watch -l app=nginx
web 擴展到 5kubectl scale sts web --replicas=5
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":5}}'
web 縮回 2kubectl scale sts web --replicas=2
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":2}}'
t1 終端,觀察 Pod 的變動情況NAME    READY   STATUS              RESTARTS   AGE
web-3   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-3   0/1     ContainerCreating   0          0
web-3   1/1     Running             0          1s
web-4   0/1     Pending             0          0s
web-4   0/1     Pending             0          0s
web-4   0/1     ContainerCreating   0          0s
web-4   1/1     Running             0          1s
web-4   1/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-3   1/1     Terminating         0          29s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-2   1/1     Terminating         0          44s
web-2   0/1     Terminating         0          44s
web-2   0/1     Terminating         0          45s
web-2   0/1     Terminating         0          45s
web-2   0/1     Terminating         0          45s
由上面可以觀察到:
另外,如果你重新獲取 StatefulSet 的 PersistentVolumeClaims,會發現擴展到 5 個副本時,掛載到 StatefulSet Pod 的 PersistentVolume 不會被刪除。這是為了讓使用者有機會就回 Volume 中的資料。
kubectl get pvc -l app=nginx
---
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
www-web-0   Bound    pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c   1Gi        RWO            standard       <unset>                 171m
www-web-1   Bound    pvc-ee106200-53cd-40cc-8cfb-b489eff8a259   1Gi        RWO            standard       <unset>                 170m
www-web-2   Bound    pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1   1Gi        RWO            standard       <unset>                 170m
www-web-3   Bound    pvc-192ba651-004b-4ef3-af6c-e95adea1a288   1Gi        RWO            standard       <unset>                 53m
www-web-4   Bound    pvc-656565e5-d3a5-4114-804e-ca14dd0c31ac   1Gi        RWO            standard       <unset>                 53m